package com.sudoku.mode;

import java.util.ArrayList;
import java.util.Iterator;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Preferences;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.sudoku.board.Tile;
import com.sudoku.observer.Event;
import com.sudoku.observer.Subject;
import com.sudoku.solver.Solver;
import com.sudoku.utility.Locator;
import com.sudoku.utility.Pair;

public class Ordinary extends Mode {

	public static int TRIES = 30;

	private Solver solver;
	private ShapeRenderer sr;

	public Ordinary(Subject subject) {
		super(subject);
		sr = new ShapeRenderer();
		solver = new Solver(board, correctBoard, subject);
		hintsUsed = 0;
	}

	public void init() {
		super.init();
		hintsUsed = 0;
		generateBoard();
		//start a new thread for the solver
		solver = new Solver(board, correctBoard, subject);
		solver.start();
	}

	@Override
	public void startGame() {
		init();

	}

	@Override
	public void solverDone() {
		ArrayList<Integer> indices = solver.getIndices();
		//delete tiles in queue and take the start tiles and make them permanent
		copyDataToBoard(indices);

		subject.notify(Event.EVENT_SOLVER_DONE);
	}

	public void generateBoard() {
		int firstRowMistakes = 1;
		int firstColMistakes = 1;
		int middleMiddleMistakes = 1;
		int bottomMiddleMistakes = 1;
		int middleRightMistakes = 1;
		int bottomRightMistakes = 1;
		boolean solved = false;
		int tries = 0;
		int maxTries = 0;

		/* solve upper left box first randomly */
		correctBoard.solveUpperLeft();

		/*
		 * Goal is to get no mistakes in the last box we solve which is bottom
		 * right box. Since the upper left is solved, we start by solving the
		 * entire upper row which solves the upper middle and upper right. 
		 * 
		 * Next we solve the left box columns which solves the middle left and lower
		 * left. After all mistakes are eliminated, we solve the middle middle
		 * box. 
		 * 
		 * Next we move to the middle right, then bottom middle. The last
		 * box, bottom right we try to solve. If there is no solution, we remake
		 * the middle middle square and the repeat the process. 
		 * 
		 * If no solution is found after 10 tries of remaking the middle, we remake the entire
		 * board
		 */
		while (!solved) {
			if (bottomRightMistakes == 0) {
				solved = true;
			}
			if (middleRightMistakes == 0 && bottomRightMistakes != 0) {
				bottomRightMistakes = correctBoard.solveBottomRight();
				tries++;
				// if its unsolvable, shuffle the middle tile
				if (tries > TRIES) {
					middleMiddleMistakes = correctBoard.solveMiddleMiddle();
					bottomMiddleMistakes = 1;
					middleRightMistakes = 1;
					tries = 0;
					maxTries++;
					if (maxTries > 10) {
						firstRowMistakes = 1;
						firstColMistakes = 1;
						middleMiddleMistakes = 1;
						bottomMiddleMistakes = 1;
						middleRightMistakes = 1;
						bottomRightMistakes = 1;
						maxTries = 0;
					}
				}
			}
			if (bottomMiddleMistakes == 0 && middleRightMistakes != 0) {
				middleRightMistakes = correctBoard.solveMiddleRight();
				tries++;
				// if its unsolvable, shuffle the middle tile
				if (tries > TRIES) {
					middleMiddleMistakes = correctBoard.solveMiddleMiddle();
					bottomMiddleMistakes = 1;
					tries = 0;
				}
			}
			if (middleMiddleMistakes == 0 && bottomMiddleMistakes != 0) {
				bottomMiddleMistakes = correctBoard.solveLowerMiddle();
				tries++;
				// if its unsolvable, shuffle the middle tile
				if (tries > TRIES) {
					middleMiddleMistakes = correctBoard.solveMiddleMiddle();
					tries = 0;
				}
			}
			if (firstRowMistakes != 0) {
				firstRowMistakes = correctBoard.solveFirstRow();
			}
			if (firstColMistakes != 0) {
				firstColMistakes = correctBoard.solveFirstColumn();
			}
			if (middleMiddleMistakes != 0 && firstColMistakes == 0 && firstRowMistakes == 0) {
				middleMiddleMistakes = correctBoard.solveMiddleMiddle();
			}
		}
	}

	/**
	 * Make all tiles blank. Make tiles permanent if they are contained in the indices
	 * @param indices
	 */
	public void copyDataToBoard(ArrayList<Integer> indices) {
		for (int i = 0; i < 81; i++) {
			Tile tile = board.getDataFromIndex(i);
			if (indices.contains(i)) {
				//make tile permanent
				permanantTiles.add(tile);
				tile.setPermanent(true);
			} else {
				tile.setBlank();
			}
		}
	}

	@Override
	public void render(SpriteBatch sb, BitmapFont font, BitmapFont pencilFont) {
		if (!debug) {
			board.drawBoxes(sb, font, pencilFont, sr);
		} else {
			correctBoard.drawBoxes(sb, font, pencilFont, sr);
		}
		drawBoard();
	}

	@Override
	public void update(float delta) {
		// TODO Auto-generated method stub

	}

	public void drawBoard() {
		float linePositionX = VIRTUAL_BOARD_PIXEL_SIZE / 9;
		float linePositionY = VIRTUAL_BOARD_PIXEL_SIZE / 9;
		sr.setTransformMatrix(Locator.getCamera().view);
		sr.setProjectionMatrix(Locator.getCamera().projection);
		sr.setColor(Color.BLACK);
		// draw horizontal lines
		for (int i = 0; i < 10; i++) {
			if (i % 3 == 0 || i == 0 || i == 10) {
				Gdx.gl20.glLineWidth(5);
			} else {
				Gdx.gl20.glLineWidth(1);
			}
			sr.begin(ShapeType.Line);
			sr.line(BOARD_START_POSITION, linePositionY * i, ACTUAL_VIRTUAL_WIDTH, linePositionY * i);
			sr.end();

		}
		// draw vertical lines
		for (int i = 0; i < 10; i++) {
			if (i % 3 == 0 || i == 0 || i == 10) {
				Gdx.gl20.glLineWidth(5);
			} else {
				Gdx.gl20.glLineWidth(1);
			}
			sr.begin(ShapeType.Line);
			sr.line(BOARD_START_POSITION + linePositionX * i, 0, BOARD_START_POSITION + linePositionX * i, ACTUAL_VIRTUAL_HEIGHT);
			sr.end();
		}

	}

	/**
	 * Debugging purposes only
	 */
	@Override
	public void solve() {
		if (solver.getOrderOfMoves().size() > 0) {
			Tile tile = solver.getOrderOfMoves().peek().getFirst();
			char data = solver.getOrderOfMoves().peek().getSecond();
			tile.setData(data);
			solver.getOrderOfMoves().remove();
		}
	}

	@Override
	public void hint() {
		if (solver.getOrderOfMoves().size() > 0) {
			Tile tile = solver.getOrderOfMoves().peek().getFirst();
			while (tile.getData() == solver.getOrderOfMoves().peek().getSecond()) {
				solver.getOrderOfMoves().remove();
				tile = solver.getOrderOfMoves().peek().getFirst();
			}
			hintsUsed++;
			tile.flashHint();
			updateHintSave();

		}
	}

	public void updateHintSave() {
		Preferences prefs = Gdx.app.getPreferences("My Preferences");
		writeSave(prefs);
		prefs.flush();
	}

	/**
	 * Save the hint list
	 */
	@Override
	public void writeSave(Preferences prefs) {
		int num = 0;
		for (Iterator<Pair<Tile, Character>> it = solver.getOrderOfMoves().iterator(); it.hasNext();) {
			Pair<Tile, Character> data = it.next();
			int index = (int) (data.getFirst().getBox().getCoords().x * COLS_PER_BOX + data.getFirst().getCoords().x + 9 * (data.getFirst().getBox()
					.getCoords().y
					* ROWS_PER_BOX + data.getFirst().getCoords().y));

			prefs.putInteger("index" + num, index);
			prefs.putString("index data" + num, Character.toString(data.getSecond()));

			num++;
		}
		prefs.putInteger("hint size", num);

		//populate perm tiles
		num = 0;
		for (Iterator<Tile> it = permanantTiles.iterator(); it.hasNext();) {
			Tile tile = it.next();
			int index = (int) (tile.getBox().getCoords().x * COLS_PER_BOX + tile.getCoords().x + 9 * (tile.getBox().getCoords().y * ROWS_PER_BOX + tile
					.getCoords().y));

			prefs.putInteger("perm index" + num, index);
			num++;
		}
		prefs.putInteger("perm size", num);

		prefs.putInteger("hintsUsed", hintsUsed);
	}

	/**
	 * Read the save list
	 */
	@Override
	public void readSave(Preferences prefs) {
		int size = prefs.getInteger("hint size", 0);
		for (int i = 0; i < size; i++) {
			Pair<Tile, Character> data = new Pair<Tile, Character>();

			int index = prefs.getInteger("index" + i, 0);
			data.setFirst(board.getDataFromIndex(index));
			data.setSecond(prefs.getString("index data" + i, "1").charAt(0));

			solver.getOrderOfMoves().add(data);
		}

		//populate perm tiles
		size = prefs.getInteger("perm size", 0);
		for (int i = 0; i < size; i++) {
			int index= prefs.getInteger("perm index" + i, 0);
			permanantTiles.add(board.getDataFromIndex(index));
		}

		hintsUsed = prefs.getInteger("hintsUsed", 0);

	}
}
